package ch.fhnw.apsi.filter;

import java.io.IOException;
import java.sql.SQLException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import ch.fhnw.apsi.Page;
import ch.fhnw.apsi.beans.Credentials;
import ch.fhnw.apsi.db.DBManager;
import ch.fhnw.apsi.password.PasswordHash;

/**
 * Servlet Filter implementation class FilterLogin
 */
@WebFilter(filterName = "filter-login", description = "Check the login status of user.", urlPatterns = { "/*" })
public final class FilterLogin implements Filter {

  public FilterLogin() {
  }

  @Override
  public void destroy() {
    // ...
  }

  @Override
  public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
      throws IOException, ServletException {
    final HttpServletRequest httpReq = (HttpServletRequest) request;
    final HttpServletResponse httpResp = (HttpServletResponse) response;
    try {
      try {
        if (!DBManager.getDB().isValid())
          throw new Exception("No DB!");
      } catch (final Exception e) {
        request.setAttribute("status", 500);
        request.setAttribute("error", "Das System steht momentan nicht zur Verfügung. Versuchen Sie es später erneut."
            + " (TIPP: MySQL starten würde helfen...)");
        throw e;
      }
      final HttpSession session = httpReq.getSession();
      synchronized (session) {
        // session.setMaxInactiveInterval(???);
        final String username = request.getParameter("username");
        final String passwort = request.getParameter("passwort");
        final boolean logout = request.getParameter("logout") != null;
        final boolean get = "GET".equals(httpReq.getMethod());
        final boolean post = "POST".equals(httpReq.getMethod());
        final Credentials login = (Credentials) session.getAttribute("login");
        final String referer = httpReq.getHeader("Referer");
        final boolean isNew = session.isNew();
        // One time redirection to see if cookies are enabled:
        final boolean redir = request.getParameter("redir") != null;
        if (isNew && (referer == null || referer.endsWith("/login")) && !redir) {
          httpResp.sendRedirect(httpResp.encodeRedirectURL(httpReq.getContextPath() + "/app/login?redir=1"));
          throw new Exception("Redirect!");
        }
        if (isNew && redir) {
          request.setAttribute("status", 400);
          request.setAttribute("error", "Sie müssen Cookies aktivieren.");
          throw new Exception("No Cookies.");
        }
        // Are both username and password set?
        final boolean both = !logout && username != null && !username.isEmpty() && passwort != null
            && !passwort.isEmpty();
        switch (httpReq.getPathInfo()) {
        case "": // missing "/" in URL
          httpResp.sendRedirect(httpResp.encodeRedirectURL(httpReq.getContextPath() + "/app/"));
          break;
        case "/login":
          if (both && post && login == null && !isNew) {
            Credentials credentials = null;
            try {
              credentials = DBManager.getDB().getCredentials(username);
            } catch (final SQLException sql) {
              request.setAttribute("status", 503);
              request.setAttribute("error", "Interner Fehler. Bitte versuchen Sie es später nochmals.");
              throw new Exception("DB not available.");
            }
            final boolean valid = credentials != null
                && PasswordHash.validatePassword(passwort, credentials.getPasswortHash());
            if (!valid) {
              request.setAttribute("status", 401);
              request.setAttribute("error", "Die Angaben zum Login sind falsch.");
              // Random wait, so that brute force takes much longer. FindBugs would not like Thread.sleep()!
              session.wait(2000 + (int) (1000 * Math.random()));
              throw new Exception("Bad login.");
            }
            session.setAttribute("login", credentials);
            httpResp.sendRedirect(httpResp.encodeRedirectURL(httpReq.getContextPath() + "/app/"));
            throw new Exception("Redirect!");
          } else if (logout || !both && get && login == null)
            session.setAttribute("login", null);
          else if (login != null) {
            request.setAttribute("status", 303);
            request.setAttribute("error", "Login erfolgreich oder bereits erfolgt. Sie werden weitergeleitet... ");
            httpResp.sendRedirect(httpResp.encodeRedirectURL(httpReq.getContextPath() + "/app/"));
            throw new Exception("Logged in already.");
          } else {
            request.setAttribute("status", 403);
            final String cookies = isNew ? "Sind Cookies aktiviert?" : "";
            request.setAttribute("error", "Login nicht erfolgreich. Überprüfen Sie Ihre Eingabe. " + cookies);
            throw new Exception("Bad Login / No Session.");
          }
          break;
        case "/register":
        case "/success":
          if (login != null) {
            request.setAttribute("status", 400);
            request.setAttribute("error", "Sie können sich nicht neu registrieren, wenn Sie bereits angemeldet sind.");
            throw new Exception("registration not allowed when logged in.");
          }
          break;
        case "/setpw":
          if (login == null) {
            request.setAttribute("status", 400);
            request.setAttribute("error", "Das Passwort kann nur geändert werden, wenn Sie angemeldet sind.");
            throw new Exception("setpw only allowed when logged in.");
          }
          break;
        case "/":
          if (login == null) {
            request.setAttribute("status", 403);
            request.setAttribute("error", "Zugriff nur erlaubt, wenn Sie angemeldet sind.");
            throw new Exception("Forbidden");
          }
          break;
        default:
          request.setAttribute("status", 404);
          request.setAttribute("error", "Diese Resource existiert nicht. Überprüfen Sie die URL.");
          throw new Exception("Bad Request/Not Found");
        }

      } // synchronized
    } catch (final Exception e) {
      Integer status = (Integer) request.getAttribute("status");
      if (status == null || status < 1)
        status = 400;
      request.setAttribute("status", status);
      request.setAttribute("page", Page.ERROR);
    } finally {
      // pass the request along the filter chain
      chain.doFilter(request, response);
    }
  }

  @Override
  public void init(final FilterConfig fConfig) throws ServletException {
    // ...
  }

}
